﻿// HelperFunctions.cs
// 
// Copyright (C) 2011 The Pennslyvania State University
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
// PURPOSE. See the GNU General Public License for more details. 
// You should have received a copy of the GNU General Public 
// License along with this program; if not, write to the Free 
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA 02111-1307 USA

#region

using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Web;
using NLog;

#endregion

namespace CosignManaged
{
    internal class BasicAuthHelpers
    {
        /// <summary>   Values that represent LogonSessionType.  </summary>
        private enum LogonSessionType : uint
        {
            Interactive = 2,
            Network,
            Batch,
            Service,
            NetworkCleartext = 8,
            NewCredentials
        }

        /// <summary>   Values that represent LogonProvider.  </summary>
        private enum LogonProvider : uint
        {
            Default = 0, // default for platform (use this!)
            WinNT35, // sends smoke signals to authority
            WinNT40, // uses NTLM
            WinNT50 // negotiates Kerb or NTLM
        }

        /// <summary>   Logon user. </summary>
        /// <param name="principal">        The principal. </param>
        /// <param name="authority">        The authority. </param>
        /// <param name="password">         The password. </param>
        /// <param name="logonType">        Type of the logon. </param>
        /// <param name="logonProvider">    The logon provider. </param>
        /// <param name="token">            [out] The token. </param>
        /// <returns>   true if it succeeds, false if it fails. </returns>
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(
            string principal,
            string authority,
            string password,
            LogonSessionType logonType,
            LogonProvider logonProvider,
            out IntPtr token);

        /// <summary>   Closes a handle. </summary>
        /// <param name="handle">   The handle. </param>
        /// <returns>   true if it succeeds, false if it fails. </returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr handle);

        /// <summary>
        /// </summary>
        private static Logger logger = GlobalLogger.Instance.GetLogger("BasicAuthHelpers");

        /// <summary>
        ///   Checks the cosign server login page to see if its available.
        /// </summary>
        /// <param name = "url">URL to check</param>
        /// <returns></returns>
        public static bool CheckCosignServer(string url)
        {
            try
            {
                WebRequest request = WebRequest.Create(url);
                var response = (HttpWebResponse)request.GetResponse();
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    logger.Warn("Cosign Server is not responding");
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                logger.WarnException("Cosign Server is not responding", ex);
                return false;
            }
        }

        /// <summary>   Authenticates the user against the specified domain. </summary>
        /// <param name="domain">   The domain. </param>
        /// <returns>   true if it succeeds, false if it fails. </returns>
        public static bool AuthenticateUser(string domain)
        {
            //Extract domain from the username as well
            string authHeader = HttpContext.Current.Request.Headers["Authorization"];
            if (authHeader != null && authHeader.StartsWith("Basic") && !string.IsNullOrEmpty("domain"))
            {
                // extract credentials from header
                string[] credentials = ExtractCredentials(authHeader);
                string username = credentials[0];
                string password = credentials[1];
                IntPtr token = IntPtr.Zero;
                try
                {
                    // Create a token for DomainName\Bob
                    // Note: Credentials should be encrypted in configuration file
                    bool result = LogonUser(username, domain, password, LogonSessionType.Network, LogonProvider.Default, out token);
                    if (result)
                    {
                        // User is verfied against the respective credentials                        
                        HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(username, "Basic"), null);                        
                        logger.Trace("User was validated for basic auth");
                    }
                    else
                    {
                        logger.Trace("User was not validated for basic auth");
                    }
                    if (token != IntPtr.Zero)
                    {
                        CloseHandle(token);
                    }

                    return result;
                }
                catch (Exception ex)
                {
                    logger.ErrorException("Unable to authenticate user with basic auth", ex);
                }
            }
            return false;
        }

        /// <summary>   Extracts the user credentials from the Authroization header. </summary>
        /// <exception cref="HttpException">    Thrown when a http error condition occurs. </exception>
        /// <param name="authHeader">   Auth header. </param>
        /// <returns>   . </returns>
        private static string[] ExtractCredentials(string authHeader)
        {
            try
            {
                // strip out the "basic"
                string encodedUserPass = authHeader.Substring(6).Trim();

                // that's the right encoding
                Encoding encoding = Encoding.GetEncoding("iso-8859-1");
                string userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass));
                int separator = userPass.IndexOf(':');

                var credentials = new string[2];
                credentials[0] = userPass.Substring(0, separator);
                credentials[1] = userPass.Substring(separator + 1);

                return credentials;
            }
            catch
            {
                throw new HttpException("Invalid Authentication Header");
            }
        }
    }
}